package com.mobilereports.view;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import com.mobilereports.model.ChartModel;
import com.mobilereports.model.ChartModelListener;
import com.mobilereports.model.Sample;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.VelocityTracker;
import android.view.View;

public class ChartView extends SurfaceView implements ChartModelListener,
		SurfaceHolder.Callback
{
	private ChartModel model;
	private ChartViewThread thread;
	private SurfaceHolder holder;
	private Context context;

	public void setComparator(Comparator<Sample> comparator)
	{
		// thread.setComparator(comparator);
		model.setComparator(comparator);
	}

	public ChartViewThread getThread()
	{
		return thread;
	}

	public ChartView(Context context)
	{
		super(context);
		initialize(context);
	}

	public ChartView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		initialize(context);
	}

	private void initialize(Context context)
	{
		holder = getHolder();
		holder.addCallback(this);
		this.context = context;
		thread = new ChartViewThread(holder, context, new Handler());
		this.setOnTouchListener(thread);
		setFocusable(true);
	}
	
	public void reinitializeThread()
	{
		if(thread != null)
		{
			surfaceDestroyed(getHolder());
		}
		holder = getHolder();
		holder.addCallback(this);
		thread = new ChartViewThread(holder, context, new Handler());
		this.setOnTouchListener(thread);
	}

	public void setModel(ChartModel model)
	{
		this.model = model;
	}

	@Override
	public void onDataChanged()
	{
		thread.onDataChanged();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder)
	{
		thread.setRunning(true);
		thread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder)
	{
		boolean retry = true;
		thread.setRunning(false);
		while (retry)
		{
			try
			{
				thread.join();
				retry = false;
			}
			catch (InterruptedException e)
			{
			}
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
	{
	}

	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus)
	{
		// thread.setRunning(hasWindowFocus);
	}

	public SurfaceHolder getSurfaceHolder()
	{
		return thread.getSurfaceHolder();
	}

	/*
	 * @Override public boolean onTouchEvent(MotionEvent event) {
	 * thread.onTouchEvent(event); return super.onTouchEvent(event); }
	 */
	/*
	 * @Override public boolean dispatchKeyEvent(KeyEvent event) { // TODO
	 * Auto-generated method stub return super.dispatchKeyEvent(event); }
	 * 
	 * @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
	 * thread.onKeyDown(event); return super.onKeyDown(keyCode, event); }
	 * 
	 * @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
	 * thread.onKeyUp(event); return super.onKeyUp(keyCode, event); }
	 */

	class ChartViewThread extends Thread implements OnTouchListener
	{
		private static final int PIXELS_PER_LETTER = 6;
		private static final double BAR_WIDTH_PERCENTAGE = 0.85;
		private static final int TOP_AXIS_LABEL_Y_OFFSET = 10;
		private static final int BAR_WIDTH = 20;
		private static final int BAR_BOTTOM_MARGIN = 10;
		private static final int BAR_WIDTH_WITH_MARGIN = BAR_WIDTH
				+ BAR_BOTTOM_MARGIN;
		private static final int TOP_AXIS_LABELS_MARGIN = 20;
		private final int[] BAR_COLORS = { 0xFF5092BC, 0xFF228B22, 0xFF9400D3,
				0xFFFFD700, 0xFFDC143C };
		private final int[] BAR_LABEL_COLORS = { 0xFFC2EDFF, 0xFFB1D63F,
				0xFFDDA0DD, 0xFFB8860B, 0xFFFFA07A };
		private static final int LABEL_COLOR = 0xFF5092BC;
		private static final int LINE_COLOR = 0xFFC2EDFF;
		private static final int HIGHLIGHT_COLOR = 0xFFB1D63F;
		private static final int BAR_ROUND_CORNER_RADIUS = 5;
		private static final int BAR_VALUE_X_OFFSET = BAR_ROUND_CORNER_RADIUS + 3;
		private static final float SHADOW_RADIUS = 3;
		private static final float SHADOW_X_OFFSET = 3;
		private static final float SHADOW_Y_OFFSET = 3;
		private static final int SHADOW_COLOR = 0XFFA0A0A0;
		private Paint[] barPaints = new Paint[BAR_COLORS.length];
		private Paint[] barLabelPaints = new Paint[BAR_LABEL_COLORS.length];
		private Paint[] barValuePaints = new Paint[BAR_COLORS.length];
		private Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		private Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		private Paint highlightPaint = new Paint();
		private Paint whitePaint = new Paint();
		private Sample highlightedSample = null;

		private SurfaceHolder surfaceHolder;
		private boolean running = false;
		private double animationFrame;
		private double animationStep = 0.05;
		

		private float startY, endY, heightOffset;

		private int viewportHeight;
		private int screenHeight;
		private int viewportOffset;
		private int maxViewportOffset;

		public ChartViewThread(SurfaceHolder surfaceHolder, Context context,
				Handler handler)
		{
			this.surfaceHolder = surfaceHolder;
			initPaints();
		}

		public void onDataChanged()
		{
//			synchronized (surfaceHolder)
//			{
				highlightedSample = null;
				viewportHeight = model.getSamples().size() * BAR_WIDTH_WITH_MARGIN
						+ TOP_AXIS_LABELS_MARGIN;
				maxViewportOffset = Math.max(viewportHeight - screenHeight, 0);
				screenHeight = getHeight();
				setMinimumHeight(viewportHeight);
				animationFrame = 0;
				viewportOffset = 0;
//			}
		}

		public void setRunning(boolean runState)
		{
			running = runState;
		}

		private void initPaints()
		{
			for (int c1 = 0; c1 < barPaints.length; c1++)
			{
				barPaints[c1] = new Paint(Paint.ANTI_ALIAS_FLAG);
				barPaints[c1].setColor(BAR_COLORS[c1]);
				barPaints[c1].setShadowLayer(SHADOW_RADIUS, SHADOW_X_OFFSET,
						SHADOW_Y_OFFSET, SHADOW_COLOR);
			}

			for (int c1 = 0; c1 < barValuePaints.length; c1++)
			{
				barValuePaints[c1] = new Paint(Paint.ANTI_ALIAS_FLAG);
				barValuePaints[c1].setColor(BAR_COLORS[c1]);
			}

			for (int c1 = 0; c1 < barLabelPaints.length; c1++)
			{
				barLabelPaints[c1] = new Paint(Paint.ANTI_ALIAS_FLAG);
				barLabelPaints[c1].setColor(BAR_LABEL_COLORS[c1]);
			}

			labelPaint.setColor(LABEL_COLOR);

			linePaint.setColor(LINE_COLOR);
			highlightPaint.setColor(HIGHLIGHT_COLOR);
			whitePaint.setColor(Color.WHITE);
		}

		@Override
		public void run()
		{
			while (running)
			{
				Canvas c = null;
				try
				{
					c = surfaceHolder.lockCanvas(null);

					synchronized (surfaceHolder)
					{
						if (animationFrame < 1.0)
							animationFrame += animationStep;
						doDraw(c);
					}
				}
				catch(Exception e) {
					Log.e("FINALLY", e.getMessage(), e);
				}
				finally
				{
					if (c != null)
						surfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}

		private void doDraw(Canvas canvas)
		{
			canvas.drawColor(Color.WHITE);

			double max = model.getMaxValue();
			double delta = 1000000000;
			while (true)
			{
				if (max > delta)
					break;
				delta /= 10;
			}

			double maxWidth = getWidth() * BAR_WIDTH_PERCENTAGE;

			for (int c1 = 0; c1 < model.getSamples().size(); c1++)
			{
				int x = 0;
				int y = BAR_WIDTH_WITH_MARGIN * c1 + TOP_AXIS_LABELS_MARGIN
						- viewportOffset;

				if (model.getSamples().get(c1).equals(highlightedSample))
					canvas.drawRect(x, (float) (y - 0.5 * BAR_BOTTOM_MARGIN),
							(float) getWidth(),
							(float) (y + BAR_WIDTH + 0.5 * BAR_BOTTOM_MARGIN),
							highlightPaint);
			}

			for (int c1 = 0; c1 < max; c1 += delta)
			{
				int x = (int) (maxWidth * c1 / max);
				canvas.drawLine(x, 0, x, getHeight(), linePaint);
			}

			canvas.drawText(String.format("%.1f", max), (float) maxWidth,
					TOP_AXIS_LABEL_Y_OFFSET, labelPaint);
			canvas.drawLine((float) maxWidth, 0, (float) maxWidth, getHeight(),
					linePaint);

			int c1 = 0;
			for (Sample s : model.getSamples())
			{
				double v = s.getValue() * animationFrame;

				int x = 0;
				int y = BAR_WIDTH_WITH_MARGIN * c1 + TOP_AXIS_LABELS_MARGIN
						- viewportOffset;
				int width = (int) (maxWidth * v / max);
				int colorIndex = GlobalState.chartModel.getSampleIndexMap().get(s.getValue());

				RectF bar = new RectF(x - BAR_ROUND_CORNER_RADIUS, y,
						x + width, y + BAR_WIDTH);

				canvas.drawRoundRect(bar, BAR_ROUND_CORNER_RADIUS,
						BAR_ROUND_CORNER_RADIUS, barPaints[colorIndex]);

				int valueLabelOffset = width + BAR_VALUE_X_OFFSET <= s
						.getLabel().length() * PIXELS_PER_LETTER ? (s
						.getLabel().length() + 1)
						* PIXELS_PER_LETTER
						+ BAR_VALUE_X_OFFSET : width + BAR_VALUE_X_OFFSET;

				canvas.drawText(String.format("%.1f", v), valueLabelOffset, y
						+ BAR_WIDTH - BAR_ROUND_CORNER_RADIUS,
						barValuePaints[colorIndex]);

				canvas.drawText(s.getLabel(), x + BAR_ROUND_CORNER_RADIUS, y
						+ BAR_WIDTH - BAR_ROUND_CORNER_RADIUS,
						barLabelPaints[colorIndex]);

				++c1;
			}
			
			canvas.drawRect(0, 0, getWidth(), TOP_AXIS_LABEL_Y_OFFSET,
					whitePaint);

			for (c1 = 0; c1 < max; c1 += delta)
			{
				int x = (int) (maxWidth * c1 / max);
				double v = max * x / maxWidth;
				canvas.drawText(String.format("%.0f", v), x + 1,
						TOP_AXIS_LABEL_Y_OFFSET, labelPaint);
				canvas.drawLine(x, 0, x, TOP_AXIS_LABEL_Y_OFFSET, linePaint);
			}	
		}

		public SurfaceHolder getSurfaceHolder()
		{
			return surfaceHolder;
		}

		@Override
		public boolean onTouch(View v, MotionEvent me)
		{
			synchronized(surfaceHolder)
			{
				VelocityTracker vt = VelocityTracker.obtain();
				vt.addMovement(me);
	
				if (me.getAction() == MotionEvent.ACTION_DOWN)
				{
					Log.i("Drag", "ACTION_DOWN");
					int y = (int) me.getY();
					int barIndex = y - TOP_AXIS_LABELS_MARGIN + viewportOffset;
					barIndex /= BAR_WIDTH_WITH_MARGIN;
					if (barIndex >= model.getSamples().size())
					{
						barIndex = model.getSamples().size() - 1;
					}
					Sample s = model.getSamples().get(barIndex);
	
					if (s.equals(highlightedSample))
					{
						model.drillDown(highlightedSample);
					}
					else
					{
						highlightedSample = s;
					}
	
					startY = me.getY();
				}
				if (me.getAction() == MotionEvent.ACTION_UP)
				{
					Log.i("Drag", "ACTION_UP");
				}
				else if (me.getAction() == MotionEvent.ACTION_MOVE)
				{
					Log.i("Drag", "ACTION_MOVE");
					endY = me.getY();
					heightOffset = startY - endY;
					startY = endY;
	
					viewportOffset += heightOffset;
					viewportOffset = Math.min(viewportOffset, maxViewportOffset);
					viewportOffset = Math.max(viewportOffset, 0);
				}
			}
			return true;
		}

		public Comparator<Sample> getComparator()
		{
			return model.getComparator();
		}
	}
}
